/*
 *  Copyright (c) 2011 Lukáš Karas <lukas.karas@centrum.cz>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License version 2 as
 *  published by the Free Software Foundation;
 *
 *  Software distributed under the License is distributed on an "AS IS"
 *  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.
 *
 *  See the GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

package cz.karry.jtw;

import com.sun.tools.attach.VirtualMachine;
import com.sun.tools.attach.VirtualMachineDescriptor;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import org.apache.log4j.BasicConfigurator;
import org.apache.log4j.Logger;
import org.apache.log4j.PropertyConfigurator;
import sun.misc.Signal;
import sun.misc.SignalHandler;

/**
 *
 * @author karry
 */
public class Main extends AbstractPoolingThread {

  private static final Logger logger = Logger.getLogger(Main.class);
  
  private static final String DEFAULT_CONFIG = "./conf/watchdog.conf";
  
  private static final String DEFAULT_LOG4J_CONFIG = "./conf/log4j.conf";
  private static final String LOG4J_CONFIG_KEY = "log4j.config";
  
  private static final String DEFAULT_STAT_DIR = "./stats/";
  private static final String STAT_DIR_KEY = "stat.dir";
  
  private static long DEFAULT_PROCESS_POOL_TIME = 30l * 1000l; // 30s
  private static final String PROCESS_POOL_TIME_KEY = "processss.pooltime";
  
  private static long DEFAULT_THREAD_POOL_TIME = 1l * 1000l; // 1s  
  private static final String THREAD_POOL_TIME_KEY = "blockedthread.pooltime";
  
  private static long DEFAULT_THREAD_WARNING_TIME = 5l * 1000l; // 5s  
  private static final String THREAD_WARNING_TIME_KEY = "blockedthread.timeout";
  

  /**
   * @param args the command line arguments
   */
  public static void main(String[] args) {
    final Main app = new Main();

    SignalHandler handler = new SignalHandler() {

      @Override
      public void handle(Signal sig) {
        logger.info("handle " + sig + " signal > ending app");
        app.stop();
        System.exit(0);
      }
    };
    Signal.handle(new Signal("TERM"), handler);//string appened to SIG, meaning SIGTERM
    Signal.handle(new Signal("INT"), handler);//string appened to SIG, meaning SIGINT

    app.start();
  }
  
  private final Map<String, ThreadWatchDog> watchDogMap = new HashMap<String, ThreadWatchDog>();
  private final String log4jConfig;
  private final String statDir;
  private final long processPoolTime;
  private final long threadPoolTime;
  private final long warningTime;

  public Main() {
    Properties props = new Properties();
    try{
    props.load(new FileInputStream(DEFAULT_CONFIG));
    }catch(IOException ioex){
      logger.error("Error while loading config file", ioex);
    }
    
    log4jConfig = props.getProperty(LOG4J_CONFIG_KEY, DEFAULT_LOG4J_CONFIG);
    statDir  = props.getProperty(STAT_DIR_KEY, DEFAULT_STAT_DIR);
    processPoolTime = Long.parseLong(props.getProperty(PROCESS_POOL_TIME_KEY, DEFAULT_PROCESS_POOL_TIME+"") );
    threadPoolTime = Long.parseLong(props.getProperty(THREAD_POOL_TIME_KEY, DEFAULT_THREAD_POOL_TIME+""));
    warningTime = Long.parseLong(props.getProperty(THREAD_WARNING_TIME_KEY, DEFAULT_THREAD_WARNING_TIME+""));
        
    // INIT LOG4J
    File configFile = new File(log4jConfig);
    if (configFile.isFile()) {
      PropertyConfigurator.configureAndWatch(configFile.getPath());
      logger.info("Log4j initialized - watching file '" + configFile + "'");
    } else {
      // Set up a simple configuration that logs on the console.
      BasicConfigurator.configure();
    }

    this.name = "processPoolThread";
  }
  
  private void start() {
    this.start(processPoolTime);
  }  

  @Override
  public boolean stop() {
    synchronized (lock) {
      if (!super.stop())
        return false;
      for (ThreadWatchDog w : watchDogMap.values()) {
        w.stop();
      }
      watchDogMap.clear();
      return true;
    }
  }

  @Override
  void doJob() {
    // get list of local JVM processes
    List<VirtualMachineDescriptor> l = VirtualMachine.list();
    Set<String> runningIds = new HashSet<String>();

    // start watchdog for new processes
    for (VirtualMachineDescriptor m : l) {
      runningIds.add(m.id());
      if ((!watchDogMap.containsKey(m.id()))
              && (!m.displayName().startsWith(this.getClass().getName()))
              && (!m.displayName().contains("JThreadWatchDog.jar"))) { // prevent watch yourself, FIXME: do it better
        ThreadWatchDog twd = new ThreadWatchDog(m, statDir, warningTime);
          twd.start(threadPoolTime);
        watchDogMap.put(m.id(), twd);
      }
    }

    // remove and stop watchdogs for stopped processes
    Set<String> copy = new HashSet<String>(watchDogMap.keySet()); // prevent Concurent modification exception
    for (String id : copy) {
      if (!runningIds.contains(id)) {
        ThreadWatchDog twd = watchDogMap.remove(id);
        twd.stop();
      }
    }
  }

}
